package tum.in.rost.pp;

import weka.core.Instance;
import weka.core.Instances;
import weka.classifiers.Classifier;
import weka.classifiers.functions.SMO;
import weka.classifiers.functions.supportVector.RBFKernel;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectInputStream;

public class SVM {

	/**
	 * @param trainData
	 * @param crossTrainData
	 */

	public static SMO SMOTOCHECK = new SMO();

	public static void trainSVM() {
		double[] crossTrainResults = new double[6];
		double[] crossTrainResult;
		SMO[] smo = new SMO[3];
		try {
			Instances trainData = new Instances(new FileReader(ProteinPrediction.TRAIN_WEKA_INPUT_FILE_PATH));
			Instances crossTrainData = new Instances(new FileReader(ProteinPrediction.CROSSTRAIN_WEKA_INPUT_FILE_PATH));
			Instances testData = new Instances(new FileReader(ProteinPrediction.TEST_WEKA_INPUT_FILE_PATH));
			trainData.setClassIndex(trainData.numAttributes() - 1);
			crossTrainData.setClassIndex(crossTrainData.numAttributes() - 1);
			testData.setClassIndex(testData.numAttributes() - 1);
			// method parameter 0: linearKernel, 1:RBFKernel
			long l = System.currentTimeMillis();
			smo[0] = setSVMParameters(1);
			smo[0].buildClassifier(trainData);
			crossTrainResult = Evaluate(smo[0], crossTrainData, false);
			crossTrainResults[0] = crossTrainResult[0];
			crossTrainResult = Evaluate(smo[0], testData, false);
			crossTrainResults[1] = crossTrainResult[0];

			smo[1] = setSVMParameters(1);
			smo[1].buildClassifier(crossTrainData);
			crossTrainResult = Evaluate(smo[1], trainData, false);
			crossTrainResults[2] = crossTrainResult[0];
			crossTrainResult = Evaluate(smo[1], testData, false);
			crossTrainResults[3] = crossTrainResult[0];

			smo[2] = setSVMParameters(1);
			smo[2].buildClassifier(testData);
			crossTrainResult = Evaluate(smo[1], trainData, false);
			crossTrainResults[4] = crossTrainResult[0];
			crossTrainResult = Evaluate(smo[1], crossTrainData, false);
			crossTrainResults[5] = crossTrainResult[0];

			double max = 0.0;
			int bestVersion = -1;
			double[] testResult = null;

			for (int i = 0; i < 6; i++) {
				if (crossTrainResults[i] > max) {
					max = crossTrainResults[i];
					bestVersion = i;

				}
			}
			/*
			 * Here i tried to compare the two models and it never matches for
			 * me. Just uncomment this block and see if this is true for u
			 * ObjectInputStream ois = new ObjectInputStream(new
			 * FileInputStream("svm.model")); SMO cls = (SMO) ois.readObject();
			 * ois.close();
			 * System.out.println("models are equal-->"+smo[0].equals(cls));
			 */

			System.out.println("Best Version: " + bestVersion + "Maximum Q2 crossTrain: " + max);
			switch (bestVersion) {
			case 0:
				testResult = Evaluate(smo[0], testData, true);
				
				weka.core.SerializationHelper.write("svm.model", smo[0]);
				break;
			case 1:
				testResult = Evaluate(smo[0], crossTrainData, true);
				weka.core.SerializationHelper.write("svm.model", smo[0]);
				break;
			case 2:
				testResult = Evaluate(smo[1], testData, true);
				weka.core.SerializationHelper.write("svm.model", smo[1]);
				break;
			case 3:
				testResult = Evaluate(smo[1], trainData, true);
				weka.core.SerializationHelper.write("svm.model", smo[1]);
				break;
			case 4:
				testResult = Evaluate(smo[2], crossTrainData, true);
				weka.core.SerializationHelper.write("svm.model", smo[2]);
				break;
			case 5:
				testResult = Evaluate(smo[2], trainData, true);
				weka.core.SerializationHelper.write("svm.model", smo[2]);
				break;
			default:
				break;
			}
			if (testResult != null) {
				System.out.println("TruePositive: " + (int) testResult[0]);
				System.out.println("TrueNegative: " + (int) testResult[1]);
				System.out.println("FalseNegative: " + (int) testResult[2]);
				System.out.println("FalsePositive: " + (int) testResult[3]);
				System.out.println("correctly classified Instances: " + (int) testResult[4]);
				System.out.println("Incorrectly classified Instances: " + (int) testResult[5]);
				System.out.println(" Q2: " + testResult[6]);
				System.out.println(" Acc(TM): " + testResult[7]);
				System.out.println(" Acc(non-TM): " + testResult[8]);
				System.out.println(" Cov(TM): " + testResult[9]);
				System.out.println(" Cov(non-TM): " + testResult[10]);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	public static SMO setSVMParameters(int option) {
		SMO smo = new SMO();
		if (option == 1) {
			RBFKernel rbf = new RBFKernel();
			smo.setKernel(rbf);
		}
		//smo.setC(0.01);
		System.out.println("C: " + smo.getC());
		System.out.println("Epsilon: " + smo.getEpsilon());
		System.out.println("Kernel: " + smo.getKernel());
		SMOTOCHECK = smo;
		return smo;
	}

	public static double[] EvaluateNew(SMO smo, Instances data) throws Exception {
		double truePositive = 0.0, trueNegative = 0.0, falsePositive = 0.0, falseNegative = 0.0;
		for (int j = 0; j < data.numInstances(); j++) {
			double predictedClass = smo.classifyInstance(data.instance(j));
			Instance everyInstance = data.instance(j);
			String trueClass = everyInstance.stringValue(everyInstance.classIndex());
			if (((int) predictedClass == 0) && (trueClass.toString().equals("+"))) {
				truePositive++;
			} else if (((int) predictedClass == 1) && (trueClass.toString().equals("-"))) {
				trueNegative++;
			} else if (((int) predictedClass == 1) && (trueClass.toString().equals("+"))) {
				falsePositive++;
			} else if (((int) predictedClass == 0) && (trueClass.toString().equals("-"))) {
				falseNegative++;
			}
		}
		return new double[] { truePositive, trueNegative, falsePositive, falseNegative };
	}

	public static double[] Evaluate(SMO smo, Instances data, boolean isTest) {
		try {
			if (isTest) {
				return calculateTestResult(EvaluateNew(smo, data));
			} else {
				return calculateCrosstrainResult(EvaluateNew(smo, data));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public static double[] calculateCrosstrainResult(double[] statistics) {
		if (statistics.length != 4) {
			throw new IllegalStateException();
		}
		return calculateCrosstrainResult(statistics[0], statistics[1], statistics[2], statistics[3]);
	}

	public static double[] calculateCrosstrainResult(double TP, double TN, double FN, double FP) {
		double[] Q2 = new double[1];
		double correct = TP + TN;
		double incorrect = FN + FP;
		Q2[0] = 100.0 * ((correct) / (correct + incorrect));
		return Q2;
	}

	public static double[] calculateTestResult(double[] statistics) {
		if (statistics.length != 4) {
			throw new IllegalStateException();
		}
		return calculateTestResult(statistics[0], statistics[1], statistics[2], statistics[3]);
	}

	public static double[] calculateTestResult(double TP, double TN, double FN, double FP) {
		double[] result = new double[11];
		result[0] = TP;
		result[1] = TN;
		result[2] = FN;
		result[3] = FP;
		result[4] = TP + TN; // correct
		result[5] = FN + FP; // incorrect
		result[6] = 100.0 * ((result[4]) / (result[4] + result[5]));// Q2
		result[7] = 100.0 * (TP / (TP + FP)); // accuracyPositive
		result[8] = 100.0 * (TN / (TN + FN)); // accuracyNegative
		result[9] = 100.0 * (TP / (TP + FN)); // coveragePositive
		result[10] = 100.0 * (TN / (TN + FP)); // coverageNegative
		return result;
	}
}